package com.vincent.redispractise.dbClass.service;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.vincent.redispractise.dbClass.entity.Shop;
import com.vincent.redispractise.dbClass.mapper.ShopMapper;
import com.vincent.redispractise.utils.CacheService;
import com.vincent.redispractise.part01.utils.RedisLogicExpireData;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.util.Optional;
import java.util.concurrent.TimeUnit;

@Slf4j
@Service
public class ShopServiceImpl extends ServiceImpl<ShopMapper, Shop> implements ShopService {
    private static final String MUTEXLOCKKEY = "lockKey:shop:";
    private static final String SHOP_PREFIX = "shop:";

    @Autowired
    private CacheService cacheService;

    @Override
    public Shop searchById(Long id) {
        //1.1使用缓存穿透(pass through)+缓存击穿互斥策略
        //return ptAndMutexLockStrategy(id);
        //1.2使用缓存穿透(pass through)+缓存击穿互斥策略（工具类）
//        return cacheService.getWithMutexLock(SHOP_PREFIX + id,MUTEXLOCKKEY +id,Shop.class,10L,TimeUnit.SECONDS,id,(p)->{
//            return Optional.ofNullable(query().eq("id", p).one()).orElse(new Shop());
//        });
        //2.1使用缓存穿透(pass through)+缓存击穿逻辑过期策略
//        return ptAndLogicExpStrategy(id);
        //2.2使用缓存穿透(pass through)+缓存击穿逻辑过期策略（工具类）
        return cacheService.getWithLogicExp(SHOP_PREFIX + id, MUTEXLOCKKEY + id, Shop.class, 10L, TimeUnit.SECONDS, id, (p) -> {
            Shop shop = Optional.ofNullable(query().eq("id", p).one()).orElse(new Shop());
            return shop;
        });
    }

    //使用缓存穿透(pass through)+缓存击穿互斥策略
    private Shop ptAndMutexLockStrategy(Long id) {
        String cacheKey = SHOP_PREFIX + id;
        //获取缓存
        Shop shop = cacheService.get(cacheKey, Shop.class);
        /*如果缓存存在，此情况下
            1.1缓存穿透只需要返回缓存值
            1.2缓存击穿：
                互斥策略：返回缓存
        */
        if (shop != null) {
            return shop;
        }
        /*如果缓存不存再，此情况下
            1.1缓存穿透：如果查询数据库没有，需要处理为空对象(其实处理缓存穿透的逻辑包含在缓存击穿的逻辑里面，无非是查询数据库的时候没有记录的处理方式)
            1.2缓存击穿：
                互斥策略：获取互斥锁
                    成功：重建缓存（）
                    失败：休眠一会，重新查询缓存，如果没有则重建缓存
        */
        //获取互斥锁
        Boolean lock = cacheService.tryLock(MUTEXLOCKKEY + id, 0, 10L, TimeUnit.SECONDS);
        //成功：重建缓存
        if (lock) {
            //如果数据库为空，设置空对象防止缓存穿透
            Shop shopVo = Optional.ofNullable(query().eq("id", id).one()).orElse(new Shop());
            cacheService.set(cacheKey, shopVo, 60 * 1L, TimeUnit.SECONDS);
            //释放锁
            cacheService.delLock(MUTEXLOCKKEY + id, null);
            return shopVo;
        } else {
            try {
                Thread.sleep(2L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //重试
            return ptAndMutexLockStrategy(id);
        }
    }

    private Shop ptAndLogicExpStrategy(Long id) {
        String cacheKey = SHOP_PREFIX + id;
        //逻辑过期，都能兼容
        RedisLogicExpireData expireData = cacheService.get(cacheKey, RedisLogicExpireData.class);
        /*如果缓存存在，此情况下
            1.1缓存穿透只需要返回缓存值
            1.2缓存击穿：
                逻辑过期：需要判断缓存是否逻辑有效，负责需要重建缓存,然后返回旧值
        */
        if (expireData != null) {
            //缓存穿透+逻辑过期
            //如果是逻辑过期，存入redis的值需要封装为有逻辑过期时间的实体，用来判断逻辑过期时间（RedisLogicExpireData）
            if (expireData.getLocalDateTime().isAfter(LocalDateTime.now())) {
                return (Shop) expireData.getData();
            } else {
                /*逻辑已过期，获取互斥锁
                    成功：开启子线程，查询数据库，更新缓存（这里考虑更新缓存还是删除缓存），主线程返回旧数据
                    失败：返回旧数据
                    返回值：不管成功失败，都是需要返回旧值；
                */
                Boolean lock = cacheService.tryLock(MUTEXLOCKKEY + id, 0, 10L, TimeUnit.SECONDS);
                if (lock) {
                    //Todo:考虑使用现成池，可控线程
                    new Thread(() -> {
                        log.info("子线程构建缓存");
                        //查询数据库
                        try {
                            //如果数据库为空，设置空对象防止缓存穿透
                            Shop shop = Optional.ofNullable(query().eq("id", id).one()).orElse(new Shop());
                            cacheService.setWithLogicExpire(cacheKey, shop, 60 * 1L, TimeUnit.SECONDS);
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                        //释放锁
                        cacheService.delLock(MUTEXLOCKKEY + id, null);
                    }
                    ).start();
                } else {
                    //获取锁失败，说明此时已经有线程在重建此缓存，返回旧值
                }
                return (Shop) expireData.getData();
            }
        } else {
            /*如果缓存不存再，此情况下(理论上逻辑过期不会出现这种没有缓存情况，需要提前预热，这里做一个容错处理)
                1.1缓存穿透：如果查询数据库没有，需要处理为空对象(其实处理缓存穿透的逻辑包含在缓存击穿的逻辑里面，无非是查询数据库的时候没有记录的处理方式)
                1.2缓存击穿：
                    逻辑过期：首次建立缓存，获取互斥锁
                        成功：重建缓存，并且返回此值（逻辑过期需要事先建立缓存）
                        失败：休眠一会，重新查询缓存,递归调用这个方法（包括有缓存，无缓存）
            */
            Boolean lock = cacheService.tryLock(MUTEXLOCKKEY + id, 0, 10L, TimeUnit.SECONDS);
            //成功：重建缓存
            if (lock) {
                //如果数据库为空，设置空对象防止缓存穿透
                Shop shopVo = Optional.ofNullable(query().eq("id", id).one()).orElse(new Shop());
                cacheService.setWithLogicExpire(cacheKey, shopVo, 60 * 1L, TimeUnit.SECONDS);
                //释放锁
                cacheService.delLock(MUTEXLOCKKEY + id, null);
                return shopVo;
            } else {
                try {
                    Thread.sleep(2L);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //重试
                return ptAndLogicExpStrategy(id);
            }
        }
    }
}
